package dao

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"strconv"
	"sync"
)

//缓冲通道的最大长度
const saveQueueLength = 1000

type FileStore struct {
	//存储映射关系的集合
	urls map[string]string
	//锁---不需要额外声明初始化
	mu sync.RWMutex
	//持久化通道
	save chan record
}

//描述映射关系的对象
type record struct {
	Key, URL string
}

func NewFileStore(filename string) *FileStore {
	fileStore := &FileStore{urls: make(map[string]string), save: make(chan record, saveQueueLength)}
	//通过文件名是否为空,判断是否是主线程,主线程才负责持久化
	if filename != "" {
		//从磁盘读取映射到内存
		if err := fileStore.load(filename); err != nil {
			log.Println("error loading data in fileStore: ", err)
		}
		//单独的持久化协程
		go fileStore.saveLoop(filename)
	}
	return fileStore
}

func (s *FileStore) Get(key, url *string) error {
	s.mu.RLock()
	defer s.mu.RUnlock()
	*url = s.urls[*key]
	fmt.Printf("根据 key=%s ,查询到的url=%s\n", *key, *url)
	return nil
}

//保存映射关系
func (s *FileStore) Put(url, key *string) error {
	for {
		*key = s.genKey(s.count())
		fmt.Printf("保存映射关系: key= %s , url= %s \n", *key, *url)
		if s.Set(*key, *url) {
			s.save <- record{*key, *url}
			return nil
		}
	}
}

//此磁盘加载映射数据到内存
func (s *FileStore) load(filename string) error {
	fmt.Printf("从[%s]文件加载映射数据\n", filename)
	file, err := openFile(filename)
	//文件读指针置位
	if _, err := file.Seek(0, 0); err != nil {
		return err
	}
	//使用JSON解码器进行读取
	d := json.NewDecoder(file)
	//读取文件,直到读取完毕
	for err == nil {
		var r record
		if err = d.Decode(&r); err == nil {
			s.Set(r.Key, r.URL)
		}
	}
	if err == io.EOF {
		return nil
	}
	fmt.Println("数据加载完毕")
	return err
}

func openFile(filename string) (*os.File, error) {
	file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		log.Fatal("FileStore:", err)
	}
	return file, err
}

func (s *FileStore) saveLoop(filename string) {
	f, _ := openFile(filename)
	defer f.Close()
	e := json.NewEncoder(f)
	for {
		// taking a record from the channel and encoding it
		r := <-s.save
		fmt.Printf("持久化映射中, key=%s,url=%s\n", r.Key, r.URL)
		if err := e.Encode(r); err != nil {
			log.Println("FileStore:", err)
		}
	}
}

func (s *FileStore) Set(smallUrl, longUrl string) bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	_, present := s.urls[smallUrl]
	if present {
		s.mu.Unlock()
		return false
	}
	s.urls[smallUrl] = longUrl
	return true
}

func (s *FileStore) count() int {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return len(s.urls)
}

func (s *FileStore) genKey(key int) string {
	return strconv.Itoa(key)
}
